#include <stdio.h>
#include <unistd.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>

void getMyPath();
void cmdProcess(char * cmd);
int processSingleCmd(char * cmd, char * dir, char * arg1, char * arg2, char * arg3);
char * mypath;

int main()
{
  getMyPath();
  printf("welcome to myshell\n");
  printf("($MYPATH is %s)\n", mypath);
  while (1) {
    char * cmd = readline("---| ");
    if (strcmp(cmd, "exit") == 0 || strcmp(cmd, "quit") == 0) 
      break;
    else
      cmdProcess(cmd);
  }
  printf("bye.\n");
  return 0;
}


/*
 * The following function processes each command string passed to the shell,
 * parsing it for command name and arguments, then passes it to the 
 * processSingleCmd function for execution.
 */
void cmdProcess(char * cmd)
{
  char path[strlen(mypath)];
  strcpy(path, mypath);

  char * args[] = {NULL, NULL, NULL};

  char * arg = NULL;
  arg = strtok(cmd, " "); // This is the first item in the list, 
                          // which is the name of the command, so we ignore it.
  
  arg = strtok(NULL, " "); // This is the first actual argument.
  
  int c=0;
  while (arg != NULL && c<3) {
    args[c] = arg;
    arg = strtok(NULL, " ");
    c++;
  }

  char * dir = NULL;
  dir = strtok(path, "#");

  int i;  
  while (dir != NULL) {
    i = processSingleCmd(cmd, dir, args[0], args[1], args[2]);
    if (i == 0) return;
    dir = strtok(NULL, "#");
  }
  if (i == -1) fprintf(stderr, "ERROR: Executable \"%s\" could not be found.\n", cmd);
    
}



/*
 * The following function executes each command passed to the shell program.
 * It contains five sections:
 *    - "&" implementation for running background processes
 *    - "<" implementation for input redirection
 *    - ">" implementation for output redirection
 *    - "+>" implementation for ouput redirection with appending
 *    - the main forking and execution process, dependant on the variables collected
 *        (or not collected) during the first four sections.
 */
int processSingleCmd(char * cmd, char * dir, char * arg1, char * arg2, char * arg3)
{
  // Implementation of & for running processes in background
  int runInBG = 0;  

  if (arg1 != NULL) {
    if (strcmp(arg1,"&") == 0) {
      runInBG = 1;
      arg1 = NULL;
    }
  }
  if (arg2 != NULL && !runInBG) {
    if (strcmp(arg2,"&") == 0) {
      runInBG = 1;
      arg2 = NULL;
    }
  }
  if (arg3 != NULL && !runInBG) {
    if (strcmp(arg3,"&") == 0) {
      runInBG = 1;
      arg3 = NULL;
    }
  }

 
  // Implementation of < for stdin redirection
  int redirInput = 0; // a boolean flag to test whether an input redirect needs to happen
  char in[1024]; //a variable to hold the name of the file containing input
  
  if (arg1 != NULL) {
    if (strcmp(arg1,"<") == 0) {
      if (arg2 == NULL) {
        fprintf(stderr, "ERROR: No redirect file specified. Aborting execution.\n");
        return -2;
      }
      redirInput = 1;
      strcpy(in, arg2);
      arg1 = NULL;
      arg2 = NULL;
    }
  }
  if (arg2 != NULL && !redirInput) {
    if (strcmp(arg2,"<") == 0) {
      if (arg3 == NULL) {
        fprintf(stderr, "ERROR: No redirect file specified. Aborting execution.\n");
        return -2;
      }
      redirInput = 1;
      strcpy(in, arg3);
      arg2 = NULL;
      arg3 = NULL;
    }
  }
  


  
  // Implementation of > for stdout redirection
  int redirOutput = 0; // a boolean flag to test whether a redirect needs to happen
  char out[1024];
  
  if (arg1 != NULL) {
    if (strcmp(arg1,">") == 0) {
      if (arg2 == NULL) {
        fprintf(stderr, "ERROR: No redirect file specified. Aborting execution.\n");
        return -2;
      }
      redirOutput = 1;
      strcpy(out, arg2);
      arg1 = NULL;
      arg2 = NULL;
    }
  }
  if (arg2 != NULL && !redirOutput) {
    if (strcmp(arg2,">") == 0) {
      if (arg3 == NULL) {
        fprintf(stderr, "ERROR: No redirect file specified. Aborting execution.\n");
        return -2;
      }
      redirOutput = 1;
      strcpy(out, arg3);
      arg2 = NULL;
      arg3 = NULL;
    }
  }


  // Implementation of +> for stdout redirection with appending
  int redirOutputAppend = 0; // a boolean flag to test whether a redirect needs to happen
  
  if (arg1 != NULL) {
    if (strcmp(arg1,"+>") == 0) {
      if (arg2 == NULL) {
        fprintf(stderr, "ERROR: No redirect file specified. Aborting execution.\n");
        return -2;
      }
      redirOutputAppend = 1;
      strcpy(out, arg2);
      arg1 = NULL;
      arg2 = NULL;
    }
  }
  if (arg2 != NULL && !redirOutputAppend) {
    if (strcmp(arg2,"+>") == 0) {
      if (arg3 == NULL) {
        fprintf(stderr, "ERROR: No redirect file specified. Aborting execution.\n");
        return -2;
      }
      redirOutputAppend = 1;
      strcpy(out, arg3);
      arg2 = NULL;
      arg3 = NULL;
    }
  }
  



  // Main execution section of function; This is where it all comes together.
  char * cName = malloc(sizeof(char) * (strlen(cmd) + strlen(dir)));
  strcpy(cName, dir);
  strcat(cName, "/");
  strcat(cName, cmd);
  if (access(cName, F_OK) == 0) { // if the executable requested exists
    pid_t pID = fork();
    if (pID == 0){
      
      // Clearly, we cannot both redirect stdout with overwriting AND with append
      if (redirOutput && redirOutputAppend) {
        fprintf(stderr, "ERROR: Cannot redirect output with overwriting AND redirect output with appending. Please choose one or the other. Aborting.\n");
        return -4;
      }
        
      if (redirInput) {
        int fd;
        close(0);
        fd = open(in, O_RDONLY);
        if (fd == -1) {
          fprintf(stderr, "ERROR: Input file %s could not be found. Aborting.\n", in);
          return -3;
        }
      }

      
      if (redirOutput) {
        int fd;
        close(1);
        fd = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0664);
        if (fd == -1) {
          fprintf(stderr, "ERROR: Could not open output file %s. Aborting.\n", out);
          return -3;
        }
      }
      else if (redirOutputAppend) {
        int fd;
        close(1);
        fd = open(out, O_WRONLY | O_CREAT | O_APPEND, 0664);
        if (fd == -1) {
          fprintf(stderr, "ERROR: Could not open output file %s. Aborting.\n", out);
          return -3;
        }
      }
      
      execl(cName, cName, arg1, arg2, arg3, NULL);
      return 0;
    }
    else {
      if(!runInBG)
        wait(NULL);
    }    
    return 0;
  }
  
  return -1; // return -1 if the executable could not be found
}



/*
 * This function very simply retrieves the "MYPATH" evironment variable and stores it
 * in the global variable "mypath".
 */
void getMyPath()
{
  mypath = getenv("MYPATH");
  if (mypath == NULL)
    mypath = "/bin#.";
}